package MelbourneMap;

import java.util.*;
import java.util.Map.Entry;
import java.awt.geom.*;
import java.io.*;

/**
 * FileIO allows scenario and data files to be read so that
 * they can be turned in to {@link Region} objects. It 
 * also allows data files and scenario files to be saved
 * for later use.
 * <p>
 * FileIO will only correctly read scenario and data files
 * that follow a specific format. Data files
 * must be placed in the same directory as the scenario file
 * or in a sub directory.
 * <p>
 * FileIO provides methods for achieving the following:
 * <ul>
 * <li>Reading a scenario file
 * <li>Reading a data file
 * <li>Reading road data and building a graph
 * <li>Saving all data files
 * <li>Saving a scenario file
 * </ul>
 * 
 * @author David Zappia, 7183216
 * @author Heong Jin Yu, 7019777
 *
 */
public class FileIO 
{
	private String path;
	String newLine = System.getProperty("line.separator");
	private Graph roadGraph;
	private Graph railGraph;
	
	/**
	 * Class constructor.
	 */
	public FileIO()
	{
        path = null;
	}
	
	/**
	 * Loads the data files as specified by the properties parameter.
	 * 
	 * @param prop				the properties object containing file information
	 * @return					a map containing data for all {@link Region} objects specified by the parameter
	 * @throws IOException		an input/output exception occurred
	 */
	public Map<String, Map<String, Region>> loadData(Properties prop) throws IOException 
	{
		Map<String, Map<String, Region>> regionMap = new HashMap<String, Map<String, Region>>();
		
		path = prop.getProperty("Path");
		
		for (Entry<Object, Object> e : prop.entrySet())
		{
			String region = e.getKey().toString();
			String file = path + "/" + e.getValue();
			
			if (region.compareToIgnoreCase("Road") == 0)
				regionMap.put(region, readRoadFile(file));
			else if (region.compareToIgnoreCase("Rail") == 0)
				regionMap.put(region, readRailFile(file));
			else if (region.compareToIgnoreCase("Path") != 0)
				regionMap.put(region, readFile(file));		
		}
		
		return regionMap;
	}
	
	/**
	 * Reads through a file storing road data, splitting the data based on
	 * road names. An {@link ArrayList} of {@link Node} objects is created 
	 * for each road which is then used to create a {@link Region} object 
	 * relating to each road.
	 * <p>
	 * Reading the road data also produces a {@link Graph} object linking
	 * every {@link Node} for every road together so that it can be used
	 * for path planning by {@link Vehicle} objects.
	 * 
	 * @param file						the name of the file to be read including the extension
	 * @return							a Map object containing Region objects (roads)
	 * @throws FileNotFoundException	if the specified file could not be found
	 * @see								Region
	 * @see								Node
	 * @see								Graph
	 * @see								Vehicle
	 */
	private Map<String, Region> readRoadFile(String file) throws FileNotFoundException
	{
		roadGraph = new Graph();
		String fileString = file; //file name and location
		Map<String, Region> roads = new HashMap<String, Region>();
		Map<String, Node> nodeList = new HashMap<String, Node>();
		Scanner scan = null;
		
		try 
		{
			scan = new Scanner(new BufferedReader(new FileReader(fileString))); //file scanner
	        String line; //a line read from the scanner
	        String[] str = null; //array of strings created when using line.split
	        
	    	String nodeKey = "", nodeKey2 = "";
	        Node n, n2;
	    	Integer type = new Integer(-1);
	    	Integer lanes = new Integer(-1);
	    	ArrayList<Node> pointList = new ArrayList<Node>();
	        
	        while (scan.hasNextLine())
	        {
	        	line = scan.nextLine(); //get the next line
	        	
	        	if (line.contains("|")) //if the line contains values
		       	{
	        		str = line.split("\\|"); //split the values
	        		
	        		for (int i = 1; i < str.length; i += 4) //store the values in the list
		       		{
		       			if (Double.valueOf(str[i].length()) > 2)
		       			{
			        		nodeKey = str[i] + "|" + str[i+1];
			        		nodeKey2 = str[i+2] + "|" + str[i+3];
			        		
			        		if (nodeList.containsKey(nodeKey) && nodeList.containsKey(nodeKey2))
			        		{
			        			n = nodeList.get(nodeKey);
			        			n2 = nodeList.get(nodeKey2);
			        		}
			        		else if (nodeList.containsKey(nodeKey))
			        		{
			        			n = nodeList.get(nodeKey);
			        			n2 = roadGraph.addNode(new Node(-1, Double.valueOf(str[i+2]), Double.valueOf(str[i+3])));
			        			nodeList.put(nodeKey2, n2);
			        		}
			        		else if (nodeList.containsKey(nodeKey2))
			        		{
			        			n2 = nodeList.get(nodeKey2);
			        			n = roadGraph.addNode(new Node(-1, Double.valueOf(str[i]), Double.valueOf(str[i+1])));
			        			nodeList.put(nodeKey, n);
			        		}
			        		else
			        		{
			        			n = roadGraph.addNode(new Node(-1, Double.valueOf(str[i]), Double.valueOf(str[i+1])));
			        			n2 = roadGraph.addNode(new Node(-1, Double.valueOf(str[i+2]), Double.valueOf(str[i+3])));
			        			nodeList.put(nodeKey, n);
			        			nodeList.put(nodeKey2, n2);
			        		}
			        		
			        		roadGraph.addEdge(new Edge(n, n2));
		        			pointList.add(n);
		        			pointList.add(n2);
		       			}
		       			else
		       			{
					       	if (Integer.valueOf(str[i]) != type)
					       		type = Integer.valueOf(str[i]);
					       	
					       	if (Integer.valueOf(str[i+1]) != lanes)
					       		lanes = Integer.valueOf(str[i+1]);
		       			}
		       		}
		       	}
	        	else if (line.compareToIgnoreCase("end") == 0) //if end of region is reached
	        	{
	        		roads.put(str[0], new Road(str[0], pointList, lanes, type));
	        		type = new Integer(-1);
	        		lanes = new Integer(-1);
	        		pointList = new ArrayList<Node>();
	        	}
	        }
		}
		finally 
	    {
			if (scan != null) //close the scanner and file
				scan.close();
		}
		
		return roads;
	}
	
	/**
	 * Reads through a file storing rail data, splitting the data based on
	 * rail names. An {@link ArrayList} of {@link Node} objects is created 
	 * for each rail which is then used to create a {@link Region} object 
	 * relating to each rail.
	 * <p>
	 * Reading the rail data also produces a {@link Graph} object linking
	 * every {@link Node} for every rail together so that it can be used
	 * for path planning by {@link Vehicle} objects.
	 * 
	 * @param file						the name of the file to be read including the extension
	 * @return							a Map object containing Region objects (rail)
	 * @throws FileNotFoundException	if the specified file could not be found
	 * @see								Region
	 * @see								Node
	 * @see								Graph
	 * @see								Vehicle
	 */
	private Map<String, Region> readRailFile(String file) throws IOException
	{
		railGraph = new Graph();
		String fileString = file; //file name and location
		Map<String, Region> rail = new HashMap<String, Region>();
		Map<String, Node> nodeList = new HashMap<String, Node>();
		Scanner scan = null;
    	int type = -2;
		
		try 
		{
			scan = new Scanner(new BufferedReader(new FileReader(fileString))); //file scanner
	        String line; //a line read from the scanner
	        String[] str = null; //array of strings created when using line.split
	        int nameCount = 1;
	        
	    	String nodeKey = "";
	        Node n, n2;
	    	ArrayList<Node> pointList = new ArrayList<Node>();
	        
	        while (scan.hasNextLine())
	        {
	        	line = scan.nextLine(); //get the next line
	        	
	        	if (line.contains("|")) //if the line contains values
		       	{
	        		str = line.split("\\|"); //split the values
	        		
	        		if (str[0].compareToIgnoreCase("line") != 0)
		       		{
	        			for (int i = 1; i < str.length;  i += 2) //store the values in the list
				       	{
				       		if (Double.valueOf(str[i].length()) > 2)
				       		{
				       			nodeKey = str[i] + "|" + str[i+1];
				       			
				       			if (nodeList.containsKey(nodeKey))
				        			n = nodeList.get(nodeKey);
				       			else
				       			{
				       				n = railGraph.addNode(new Node(-1, Double.valueOf(str[i]), Double.valueOf(str[i+1])));
				        			nodeList.put(nodeKey, n);
				       			}
				       			
				       			if (pointList.size() > 0)
				       			{
				        			n2 = pointList.get(pointList.size() - 1);
					       			railGraph.addEdge(new Edge(n, n2));
				       			}
				       			
			        			pointList.add(n);
				       		}
				       	}
		       		}
		       	}
	        	else if (line.compareToIgnoreCase("end") == 0) //if end of region is reached
	        	{
	        		if (rail.containsKey(str[0]))
		       		{
		       			str[0] = str[0] + nameCount;
		       			nameCount++;
		       		}
	        		
	        		if (str[0].compareToIgnoreCase("line") != 0)	        		
	        			rail.put(str[0], new Region(str[0], pointList, type));
	        		
	        		pointList = new ArrayList<Node>();
	        	}
	        }
		}
		finally 
	    {
			if (scan != null) //close the scanner and file
				scan.close();
		}
	        		
		
		return rail;
	}
		
	/**
	 * Reads through a the specified region data file splitting up the data into sub 
	 * regions. The data is for each sub region is placed into an {@link ArrayList} 
	 * of {@link Node} storing all the data points.
	 * 
	 * @param file			the data file to be read
	 * @return				a Map containing regions and their points
	 * @throws IOException	if an input or output exception occurred
	 * @see 				Map
	 * @see					ArrayList
	 * @see					Node
	 */
	private Map<String, Region> readFile(String file) throws IOException
	{
		String fileString = file; //file name and location
		Map<String, Region> regions = new HashMap<String, Region>();
		Scanner scan = null;
		int type = -1;
		
		try 
		{
			scan = new Scanner(new BufferedReader(new FileReader(fileString))); //file scanner
	        String line; //a line read from the scanner
	        String[] str = null; //array of strings created when using line.split
	        
	        int nameCount = 1;
	        ArrayList<Node> pointList = new ArrayList<Node>(); //temporary list of points
	    	
	        while (scan.hasNextLine())
	        {
	        	line = scan.nextLine(); //get the next line
	        	
	        	if (line.contains("|")) //if the line contains values
		       	{
		       		str = line.split("\\|"); //split the values
		       		
		       		for (int i = 1; i < str.length;  i += 2) //store the values in the list
			       	{
			       		if (Double.valueOf(str[i].length()) > 2)
			       			pointList.add(new Node(-1, Double.valueOf(str[i]), Double.valueOf(str[i+1])));
			       	}
		       	}
	        	else if (line.compareToIgnoreCase("end") == 0 && str[0].compareToIgnoreCase("line") != 0) //if end of region is reached
	        	{
		       		if (regions.containsKey(str[0]))
		       		{
		       			str[0] = str[0] + nameCount;
		       			nameCount++;
		       		}
		       		
	        		regions.put(str[0], new Region(str[0], pointList, type)); //create a new map key for the region with an empty point list
	        		pointList = new ArrayList<Node>();
	        	}
	        	
	        	else if (line.compareToIgnoreCase("end") == 0)
	        	{
	        			pointList = new ArrayList<Node>();
	        	}
	        }
		}
		finally 
	    {
			if (scan != null) //close the scanner and file
				scan.close();
		}
		
		return regions;
	}
	
	/**
	 * Saves data files in the correct format into their correct files 
	 * using the specified {@link Map} of {@link Region} objects points. 
	 * A string is built by reading the data points and adding a new line 
	 * for each pair of point coordinates and ending each set of points with 
	 * "end" on a new line. Data is written into the file creating it if it 
	 * does not exist and over writing it if it does. No attempt is made to 
	 * check if the file exists before over writing it.
	 * 
	 * @param fileName			the specified name of the overlay file
	 * @param map				a Map object containing the overlay region point data
	 * @throws IOException		if an input or output exception occurred
	 * @see 					Region
	 * @see						ArrayList
	 * @see						Node
	 */
	public void saveData(String fileName, Map<String, Region> map) throws IOException
	{
	    //iterate through points by key and save each one into a data file.
		File file = new File(path + "\\" + fileName);
		
		Writer fileWriter = new BufferedWriter(new FileWriter(file));
		
		Iterator it = map.keySet().iterator();
		fileWriter.write("");
        while (it.hasNext())
        {
        	String key = it.next().toString();
        	ArrayList<Node> pointList = map.get(key).getData();
        	Entry<String, Region> entry = map.entrySet().iterator().next();
        	
        	if (entry.getValue().getType() > -1)
        	{
        		int type = map.get(key).getType();
            	int lanes = ((Road) map.get(key)).getLanes();
        		
        		for (int i = 0; i < pointList.size(); i += 2)
        			fileWriter.append(key + "|" + pointList.get(i).getX() + "|" + pointList.get(i).getY() + 
	            					"|" + pointList.get(i+1).getX() + "|" + pointList.get(i+1).getY() + 
	            					"|" + type + "|" + lanes + newLine);
        	}
        	else
        	{
	            for (int i = 0; i < pointList.size(); i++)
	            	fileWriter.append(key + "|" + pointList.get(i).getX() + "|" + pointList.get(i).getY() + newLine);
        	}
        	fileWriter.append("end" + newLine);
        }
        
        fileWriter.append("finalend" + newLine);
		
        fileWriter.close();
	  }
	
	/**
	 * Saves a scenario file in the current location with the name specified
	 * name using the correct format. A string with all the necessary
	 * data is built first. Once the string has been completed it 
	 * is written into the specified file creating it if it does 
	 * not exist and over writing it if it does. No attempt is 
	 * made to check if the file exists before over writing it.
	 * 
	 * @param name				the name of the scenario file
	 * @throws IOException		if an input or output exception occurred
	 */
	public void saveProperties(Properties prop) throws IOException
	{
		String file = path + "/default.properties";
		
		prop.store(new FileOutputStream(file), "Default Properties File");
	}
	
	/**
	 * Gets the {@link Graph} object used to store the interconnecting 
	 * road network of {@link Node} objects.
	 * 
	 * @return				a Graph object relating to the road network
	 * @see 				Graph
	 */
	public Graph getRoadGraph()
	{
		return roadGraph;
	}
	
	/**
	 * Gets the {@link Graph} object used to store the interconnecting 
	 * road network of {@link Node} objects.
	 * 
	 * @return				a Graph object relating to the rail network
	 * @see 				Graph
	 */
	public Graph getRailGraph()
	{
		return railGraph;
	}
}

